package info.xiancloud.plugin.monitor.common;

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import info.xiancloud.core.distribution.NodeStatus;
import info.xiancloud.core.distribution.event.NodeOfflineEvent;
import info.xiancloud.core.distribution.service_discovery.ApplicationDiscovery;
import info.xiancloud.core.distribution.service_discovery.Instance;
import info.xiancloud.core.event.IEventListener;
import info.xiancloud.core.message.id.NodeIdBean;
import info.xiancloud.core.util.LOG;

import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 静态nodeId分配器，将动态nodeId映射为静态不可变的固定id
 *
 * @author happyyangyuan
 */
public class StaticNodeIdManager {
    //nodeId:n
    private static final Map<String, Integer> nodeId_to_StaticFinalId_map = new ConcurrentHashMap<>();
    private static final LoadingCache<String, List<Integer>> application_to_freeId_map = CacheBuilder.newBuilder()
            .build(new CacheLoader<String, List<Integer>>() {
                public List<Integer> load(String application) {
                    return new LinkedList<>();
                }
            });
    private static final LoadingCache<String, LinkedList<Integer>> application_to_takenId_map = CacheBuilder.newBuilder()
            .build(new CacheLoader<String, LinkedList<Integer>>() {
                public LinkedList<Integer> load(String application) throws Exception {
                    return new LinkedList<>();
                }
            });

    /**
     * Lasy-create staticNodeId for the given node id.
     *
     * @param nodeId the node id generated by the node itself.
     * @return the staticNodeId eg. payment---0
     */
    public static String getStaticNodeId(String nodeId) {
        String application = NodeIdBean.parse(nodeId).getApplication();
        if (nodeId_to_StaticFinalId_map.get(nodeId) == null) {
            Instance<NodeStatus> instance = ApplicationDiscovery.singleton.instance(nodeId);
            if (instance != null) {
                LOG.debug("Instance with given nodeId is found online, then initialize its static node id and put it into the map.");
                onEvent(nodeId);
            } else {
                LOG.warn(new IllegalArgumentException("Bad node id or the node is offline: " + nodeId));
            }
        }
        return nodeId_to_StaticFinalId_map.get(nodeId) == null ?
                application + NodeIdBean.splitter + "null" ://it represents a none exited static id.
                application + NodeIdBean.splitter + nodeId_to_StaticFinalId_map.get(nodeId);
    }

    private static void onEvent(String nodeId) {
        NodeIdBean nodeIdBean = NodeIdBean.parse(nodeId);
        String application = nodeIdBean.getApplication();
        List<Integer> freeIds, takenIds;
        freeIds = application_to_freeId_map.getUnchecked(application);
        takenIds = application_to_takenId_map.getUnchecked(application);
        synchronized (freeIds) {
            if (nodeId_to_StaticFinalId_map.get(nodeId) != null) return;
            if (freeIds.isEmpty()) {
                LOG.debug("无空缺，新增一个静态id");
                int next;
                if (takenIds.isEmpty()) {
                    next = /*1*/0;
                } else {
                    next = Collections.max(takenIds) + 1;
                }
                takenIds.add(next);
                nodeId_to_StaticFinalId_map.put(nodeId, next);
            } else {
                LOG.debug("有空缺");
                int lackedToTake = freeIds.remove(0);
                takenIds.add(lackedToTake);
                nodeId_to_StaticFinalId_map.put(nodeId, lackedToTake);
            }
        }
    }

    public static class NodeLostEventListener implements IEventListener {

        @Override
        public boolean async() {
            return true;
        }

        @Override
        public void onEvent(Object eventObject) {
            NodeOfflineEvent event = (NodeOfflineEvent) eventObject;
            String nodeId = event.getInstance().getNodeId();
            NodeIdBean nodeIdBean = NodeIdBean.parse(nodeId);
            String application = nodeIdBean.getApplication();
            List<Integer> freeIds, takenIds;
            freeIds = application_to_freeId_map.getUnchecked(application);
            takenIds = application_to_takenId_map.getUnchecked(application);
            synchronized (freeIds) {
                Integer takenIdToFree = nodeId_to_StaticFinalId_map.remove(nodeId);
                if (takenIdToFree != null) {
                    freeIds.add(takenIdToFree);
                    takenIds.remove(takenIdToFree);
                }
            }
        }

        @Override
        public Class<?> getEventClass() {
            return NodeOfflineEvent.class;
        }
    }

    /*
    en: Currently, only new node's 'NodeOnlineEvent' will be fired, no NodeOnlineEvents are fired for the existed nodes.
    cn: 当前，监听节点只能收到新节点的上线事件，而存量节点的上线事件，它收不到。
    public static class NodeAddedEventListener implements IEventListener {

        @Override
        public void onEvent(Object eventObject) {
            NodeOnlineEvent event = (NodeOnlineEvent) eventObject;
            String nodeId = event.getInstance().getNodeId();
            NodeIdBean clientIdBean = NodeIdBean.parse(nodeId);
            String application = clientIdBean.getApplication();
            List<Integer> freeIds, takenIds;
            freeIds = application_to_freeId_map.getUnchecked(application);
            takenIds = application_to_takenId_map.getUnchecked(application);
            synchronized (freeIds) {
                if (freeIds.isEmpty()) {
                    LOG.debug("无空缺，新增一个静态id");
                    int next;
                    if (takenIds.isEmpty()) {
                        next = 0;
                    } else {
                        next = Collections.max(takenIds) + 1;
                    }
                    takenIds.add(next);
                    nodeId_to_StaticFinalId_map.put(nodeId, next);
                } else {
                    LOG.debug("有空缺");
                    int lackedToTake = freeIds.remove(0);
                    takenIds.add(lackedToTake);
                    nodeId_to_StaticFinalId_map.put(nodeId, lackedToTake);
                }
            }
        }

        @Override
        public Class<?> getEventClass() {
            return NodeOnlineEvent.class;
        }
    }*/

}
